package client

import (
	"fsd/fight"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"fsd/logger"
	"go.uber.org/zap"
)

type ClientManager struct {
	fight.GameProtocoler
	ServerAddr string
	U2G        map[int32]uint64
	waitGroup  *sync.WaitGroup
}

var CM *ClientManager = &ClientManager{
	U2G:       make(map[int32]uint64),
	waitGroup: &sync.WaitGroup{},
}

func StartClients() {
	for userId, fightId := range CM.U2G {
		c, err := CM.GameProtocoler.Dial(CM.ServerAddr)
		if err != nil {
			panic(err.Error())
			return
		}
		cli := &client{
			userId:      userId,
			fightId:     fightId,
			conn:        c,
			recvNetChan: make(chan *fight.WrapMsg, 128),
			sendNetChan: make(chan []byte, 128),
			ticker:      time.NewTicker(5 * time.Second),
			frameTicker: &time.Ticker{},
			overTimer:   &time.Timer{},
			rw:          CM.NewRW(),
		}
		CM.waitGroup.Add(1)
		go cli.cliR()
		go cli.cliW()
		go cli.cliH()

	}
	CM.waitGroup.Wait()
	time.Sleep(500 * time.Millisecond)
	logger.Debug("exit")
}

type client struct {
	userId      int32
	fightId     uint64
	conn        net.Conn
	recvNetChan chan *fight.WrapMsg
	sendNetChan chan []byte
	ticker      *time.Ticker
	frameTicker *time.Ticker
	overTimer   *time.Timer
	closeOnce   sync.Once
	closeFlag   int32
	cliState    int
	gameState   fight.GameState
	selfFrame   int32
	rw          fight.ReadWriter
}

func (cli *client) stop() {
	cli.closeOnce.Do(func() {
		atomic.StoreInt32(&cli.closeFlag, 1)
		cli.ticker.Stop()
		if cli.overTimer.C != nil {
			cli.overTimer.Stop()
		}
		if cli.frameTicker != nil {
			cli.frameTicker.Stop()
		}
	})
}

func decodeReadBs(bs []byte) (*fight.WrapMsg, error) {
	cmd := fight.GetCmdByBS(bs)
	wm, err := fight.NewS2CWrapMsg(cmd, CM.GameProtocoler)
	if err != nil {
		return nil, err
	}
	err = wm.NewDecodeWrapMsg(bs)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

func (cli *client) cliR() {
	defer func() {
		close(cli.recvNetChan)
	}()

	for {
		bs, err := cli.rw.Read(cli.conn)
		if err != nil {
			logger.Debug("read err", zap.Error(err))
			return
		}
		// 解包
		var wm *fight.WrapMsg
		wm, err = decodeReadBs(bs)
		if err != nil {
			return
		}
		cli.recvNetChan <- wm
	}
}

func (cli *client) cliW() {
	defer func() {
		cli.conn.Close()
	}()

	for {
		select {
		case bs, ok := <-cli.sendNetChan:
			if !ok {
				return
			}
			if _, err := cli.rw.Write(cli.conn, bs); err != nil {
				return
			}
		}
	}
}

func (cli *client) cliH() {
	defer func() {
		close(cli.sendNetChan)
		cli.stop()
		CM.waitGroup.Done()
	}()

	cli.doLogin()
	cli.doHeartBeat()

	for {
		select {
		case <-cli.ticker.C:
			cli.doHeartBeat()

		case wm, ok := <-cli.recvNetChan:
			if !ok {
				return
			}
			if !cli.handleS2CMsg(wm) {
				return
			}

		case <-cli.frameTicker.C:
			cli.doUpload()
		case <-cli.overTimer.C:
			cli.doSettle()
		}
	}
}

func (cli *client) handleS2CMsg(wm *fight.WrapMsg) bool {
	switch wm.Cmd {
	case fight.PT_CMD_LOGIN:
		if fight.PT_ACK_RET_OK != wm.Msg.GetLoginAckRet() {
			logger.Warn("login fail", zap.Int32("userId", cli.userId))
			return false
		}
		logger.Debug("login ok", zap.Int32("userId", cli.userId))

		// // reconnect
		// newGameState := fight.GameState(realPB.GetGameState())
		// switch newGameState {
		// case fight.GAME_STATE_LOADING:
		//     time.AfterFunc(100*time.Millisecond, cli.doLoading)
		// case fight.GAME_STATE_TIMELINE:
		//     time.AfterFunc(100*time.Millisecond, cli.doTimeline)
		// case fight.GAME_STATE_START:
		//     cli.startFight()
		// }
	case fight.PT_CMD_LOADING:
	case fight.PT_CMD_HEART_BEAT:
	case fight.PT_CMD_TIMELINE:
		cli.overTimer = time.NewTimer(6 * time.Second)
		cli.startFight()
	case fight.PT_CMD_ALL_LOGINED:
		time.AfterFunc(100*time.Millisecond, cli.doLoading)
	case fight.PT_CMD_ALL_LOADED:
		time.AfterFunc(100*time.Millisecond, cli.doTimeline)
	case fight.PT_CMD_BROADCAST:
		logger.Debug("frame data", zap.Any("data", wm.Msg))
	case fight.PT_CMD_SETTLE:
		logger.Debug("recv settled", zap.Any("results", wm.Msg))
		return false
	default:
		logger.Warn("recv other cmd", zap.Any("cmd", wm.Cmd))
	}
	return true
}

func (cli *client) doLogin() {
	wmReq, err := fight.NewC2SWrapMsg(fight.PT_CMD_LOGIN, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wmReq.Msg.SetLoginReqUserId(cli.userId)
	wmReq.Msg.SetLoginReqFightId(cli.fightId)
	wmReq.NewEncodeWrapMsg()
	cli.sendNetChan <- wmReq.Bs
	cli.cliState = fight.PLAYER_STATE_LOGINED
}

func (cli *client) doHeartBeat() {
	wmReq, err := fight.NewC2SWrapMsg(fight.PT_CMD_HEART_BEAT, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wmReq.NewEncodeWrapMsg()
	cli.sendNetChan <- wmReq.Bs
}

func (cli *client) doLoading() {
	wmReq, err := fight.NewC2SWrapMsg(fight.PT_CMD_LOADING, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wmReq.NewEncodeWrapMsg()
	cli.sendNetChan <- wmReq.Bs
	cli.cliState = fight.PLAYER_STATE_LOADED
}

func (cli *client) doTimeline() {
	wmReq, err := fight.NewC2SWrapMsg(fight.PT_CMD_TIMELINE, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wmReq.NewEncodeWrapMsg()
	cli.sendNetChan <- wmReq.Bs
	cli.cliState = fight.PLAYER_STATE_START
}

func (cli *client) doUpload() {
	cli.selfFrame++
	wm, err := fight.NewC2SWrapMsg(fight.PT_CMD_UPLOAD, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wm.Msg.SetUploadFrameCmdId(1)
	wm.Msg.SetUploadFrameId(cli.selfFrame)
	wm.Msg.SetUploadFrameData([]byte{5, 2, 0})
	wm.NewEncodeWrapMsg()
	cli.sendNetChan <- wm.Bs
}

func (cli *client) doSettle() {
	wm, err := fight.NewC2SWrapMsg(fight.PT_CMD_SETTLE, CM.GameProtocoler)
	if err != nil {
		logger.Warn("err", zap.Error(err))
		return
	}
	wm.Msg.SetSettleReqResult(int32(1))
	wm.NewEncodeWrapMsg()
	cli.sendNetChan <- wm.Bs
	cli.frameTicker.Stop()
	logger.Debug("do settle")
}

func (cli *client) startFight() {
	cli.frameTicker = time.NewTicker(time.Duration(fight.Srv.GameRoundTime))
}
